
#include <ogdl/network/Impl/SocketWin32.h>
#include <sstream>

namespace ogdl
{

    unsigned int SocketImpl::_count = 0;

    SocketImpl::SocketImpl()
        : _sock(INVALID_SOCKET)
    {
        ZeroMemory(&_sin, sizeof(_sin));
        _len = sizeof(_sin);
        _err = "";

        ++_count;
        SocketImpl::startup();
    }

    SocketImpl::SocketImpl(SocketImpl const &t)
        : _sock(t._sock), _domain(t._domain), _type(t._type), _err(t._err), _sin(t._sin), _len(t._len)
    {
        ++SocketImpl::_count;
        SocketImpl::startup();
    }

    SocketImpl::~SocketImpl()
    {
        --SocketImpl::_count;
        SocketImpl::cleanup();
    }

    SocketImpl & SocketImpl::operator=(SocketImpl const &t)
    {
        if (this != &t)
        {
            _sock = t._sock;
            _domain = t._domain;
            _type = t._type;
            _err = t._err;
            _sin = t._sin;
            _len = t._len;
        }
        return *this;
    }

    void SocketImpl::startup()
    {
        WSADATA wsa;

        if (SocketImpl::_count == 0)
        {
            WSAStartup(MAKEWORD(2, 2), &wsa);
        }    
    }

    void SocketImpl::cleanup()
    {
        if (SocketImpl::_count == 0)
        {
            WSACleanup();
        }
    }

    void SocketImpl::socket(net::SockDomain domain, net::SockType type)
    {
        int d, t;

        this->_domain = domain;
        this->_type = type;

        d = (domain == net::IPv4 ? AF_INET : AF_INET6);
        t = (type == net::STREAM ? SOCK_STREAM : SOCK_DGRAM);
        _sock = WSASocket(d, t, 0, NULL, 0, 0);
        if (_sock == INVALID_SOCKET)
        {
            std::ostringstream oss;

            oss << WSAGetLastError();
            _err = oss.str();
        }
    }

    void SocketImpl::bind(std::string const &host, int port)
    {
        _sin.sin_family = (_domain == net::IPv4 ? AF_INET : AF_INET6);
        _sin.sin_addr.s_addr = inet_addr(host.c_str());
        _sin.sin_port = htons(port);

        if (::bind(_sock, (SOCKADDR *)&_sin, _len) == SOCKET_ERROR)
        {
            std::ostringstream oss;

            oss << WSAGetLastError();
            _err = oss.str();
            this->close();
        }
    }

    void SocketImpl::listen(int backlog)
    {
        if (::listen(_sock, backlog) == SOCKET_ERROR)
        {
            std::ostringstream oss;

            oss << WSAGetLastError();
            _err = oss.str();
            this->close();
        }
    }

    void SocketImpl::accept(ISocket *client)
    {
        SocketImpl *c = dynamic_cast<SocketImpl *>(client);

        WSAAccept(c->_sock, (SOCKADDR *)&c->_sin, (LPINT)c->_len, NULL, NULL);
        if (c->_sock == INVALID_SOCKET)
        {
            std::ostringstream oss;

            oss << WSAGetLastError();
            _err = oss.str();
            this->close();
        }
        c->_domain = _domain;
        c->_type = _type;
    }

    void SocketImpl::connect(std::string const &host, int port)
    {
        _sin.sin_family = (_domain == net::IPv4 ? AF_INET: AF_INET6);
        _sin.sin_addr.s_addr = inet_addr(host.c_str());
        _sin.sin_port = htons(port);

        if (WSAConnect(_sock, (SOCKADDR *)&_sin, _len, NULL, NULL, NULL, NULL) == SOCKET_ERROR)
        {
            std::ostringstream oss;

            oss << WSAGetLastError();
            _err = oss.str();
            this->close();
        }
    }

    void SocketImpl::close()
    {
        if (closesocket(_sock) == SOCKET_ERROR)
        {
            std::ostringstream oss;
            
            oss << WSAGetLastError();
            _err = oss.str();
        }
    }

    void SocketImpl::read(void *buf, size_t len, int flags)
    {
        LPWSABUF buffer = static_cast<LPWSABUF>(buf);
        DWORD length = len;

        if (_domain == net::STREAM)
        {
            // TCP
            if (WSARecv(_sock, buffer, length, NULL, NULL, NULL, NULL) != 0)
            {
                std::ostringstream oss;

                oss << WSAGetLastError();
                _err = oss.str();
            }
        }
        else
        {
            // UDP
            DWORD rb = 0;

            if (WSARecvFrom(_sock, buffer, length, &rb, 0, (SOCKADDR *)&_sin, (LPINT)_len, NULL, NULL) != 0)
            {
                std::ostringstream oss;

                oss << WSAGetLastError();
                _err = oss.str();

            }
        }
    }
    
    void SocketImpl::write(void *buf, size_t len, int flags)
    {
        LPWSABUF buffer = static_cast<LPWSABUF>(buf);
        DWORD length = len;

        if (_domain == net::STREAM)
        {
            // TCP
            if (WSASend(_sock, buffer, length, NULL, NULL, NULL, NULL) != 0)
            {
                std::ostringstream oss;

                oss << WSAGetLastError();
                _err = oss.str();
            }
        }
        else
        {
            // UDP
            DWORD rb = 0;

            if (WSASendTo(_sock, buffer, length, &rb, 0, (SOCKADDR *)&_sin, _len, NULL, NULL) != 0)
            {
                std::ostringstream oss;

                oss << WSAGetLastError();
                _err = oss.str();

            }
        }
    }

    std::string const & SocketImpl::lastError() const
    {
        return _err;
    }

}
